package io.github.lhdxhl.wechatpay.v3;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.github.lhdxhl.wechatpay.enums.WxPayV3Type;
import io.github.lhdxhl.wechatpay.enums.WxServerType;
import io.github.lhdxhl.wechatpay.exception.PayException;
import io.github.lhdxhl.wechatpay.param.WxPayTransParam;
import io.github.lhdxhl.wechatpay.param.WxRefundParam;
import io.github.lhdxhl.wechatpay.param.WxTransQueryParam;
import io.github.lhdxhl.wechatpay.property.WxPayProperties;
import io.github.lhdxhl.wechatpay.result.*;
import io.github.lhdxhl.wechatpay.utils.WxPaySignUtil;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.net.URI;
import java.security.PrivateKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author lsk
 * @version 1.0.0
 * @ClassName WxPayV3ApiService.java
 * @Description 微信支付V3Api 接口
 * @createTime 2022年09月21日 11:35:00
 */
public class WxJsPayV3Api extends AbstractV3Api{

    /**
     * 构造函数
     *
     * @param client
     */
    public WxJsPayV3Api(WxPayClient client) {
        super(client);
    }


    /**
     * @title jsPay
     * @description 预支付请求接口
     * @author lsk
     * @param: payParam
     * @updateTime 2022/9/30 14:00
     * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<io.github.lhdxhl.wechatpay.result.WxJsPayResult>
     * @throws IOException
     */
    public WxResponseEntity<WxJsPayResult> prepayment(WxPayTransParam payParam) throws IOException {
        WxResponseEntity<WxJsPayResult> wxResponseEntity = new WxResponseEntity<>();
        WxPayProperties config = this.getConfig();
        this.client().withType(WxPayV3Type.JSAPI, payParam)
                .function(this::jsPayFunction)
                .consumer(responseEntity -> {
                    ObjectNode body = (ObjectNode) responseEntity.getBody();
                    if (Objects.isNull(body)) {
                        throw new PayException("response body cannot be resolved");
                    }
                    WxPayTransResult payTransResult = this.getMapper().convertValue(body, WxPayTransResult.class);
                    if(Objects.isNull(payTransResult)){
                        throw new PayException("response body parse payTransResult is wrong");
                    }
                    String packageStr = "prepay_id=" + payTransResult.getPrepayId();
                    long  timeStamp = new Date().getTime();
                    String nonceStr = WxPaySignUtil.generateNonceStr();
                    PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(config.getMchPrivateKey());
                    try {
                        String paySign = WxPaySignUtil.paySignFromJSApi(config.getAppId(),timeStamp,nonceStr, packageStr, merchantPrivateKey);
                        WxJsPayResult result = new WxJsPayResult();
                        result.setAppId(config.getAppId());
                        result.setNonceStr(nonceStr);
                        result.setPaySign(paySign);
                        result.setPackageValue(packageStr);
                        result.setTimeStamp(String.valueOf(timeStamp));
                        wxResponseEntity.setHttpStatus(responseEntity.getHttpStatus());
                        wxResponseEntity.setBody(result);
                    } catch (Exception e) {
                        PayException payException = new PayException("wechat pay sign error , result : " +  e);
                        payException.setResponse(responseEntity);
                        throw payException;
                    }
                })
                .request();
        return wxResponseEntity;
    }

  /**
   * @title queryTransById
   * @description 通过微信支付订单号查询账单
   * @author lsk
   * @param: params
   * @updateTime 2022/9/30 21:08
   * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<io.github.lhdxhl.wechatpay.result.WxPayNoticeResult>
   * @throws IOException
   */
    public WxResponseEntity<WxPayInfoResult> queryTransById(WxTransQueryParam param) throws IOException {
        WxResponseEntity<WxPayInfoResult> wxResponseEntity = new WxResponseEntity<>();
        this.client().withType(WxPayV3Type.TRANSACTION_TRANSACTION_ID, param)
                .function(this::queryTransFunction)
                .consumer(responseEntity->{
                    ObjectNode body = (ObjectNode) responseEntity.getBody();
                    if (Objects.isNull(body)) {
                        throw new PayException("response body cannot be resolved");
                    }
                    WxPayInfoResult wxPayInfoResult = this.getMapper().convertValue(body, WxPayInfoResult.class);
                    if(Objects.isNull(wxPayInfoResult)){
                        throw new PayException("response body parse wxPayInfoResult is wrong");
                    }
                    wxResponseEntity.setHttpStatus(responseEntity.getHttpStatus());
                    wxResponseEntity.setBody(wxPayInfoResult);
                })
                .request();
        return wxResponseEntity;
    }

    /**
     * @title queryTransFunction
     * @description 订单查询 function
     * @author lsk
     * @param: type
     * @param: params
     * @updateTime 2022/9/30 20:54
     * @return: org.apache.http.client.methods.HttpUriRequest
     * @throws
     */
    private HttpUriRequest queryTransFunction(WxPayV3Type type, WxTransQueryParam params) {

        WxPayProperties config = this.getConfig();

        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        queryParams.add("mchid", config.getMchId());

        URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WxServerType.CHINA))
                .queryParams(queryParams)
                .build()
                .expand(params.getTransIdOrOutTradeNo())
                .toUri();
        return Get(uri);
    }


    /**
     * @title queryTransByOutTradeNo
     * @description 通过商户订单号查询订单
     * @author lsk
     * @param: param
     * @updateTime 2022/9/30 23:01
     * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<io.github.lhdxhl.wechatpay.result.WxPayInfoResult>
     * @throws IOException
     */
    public WxResponseEntity<WxPayInfoResult> queryTransByOutTradeNo(WxTransQueryParam param) throws IOException {
        WxResponseEntity<WxPayInfoResult> wxResponseEntity = new WxResponseEntity<>();
        this.client().withType(WxPayV3Type.TRANSACTION_OUT_TRADE_NO, param)
                .function(this::queryTransFunction)
                .consumer(responseEntity->{
                    ObjectNode body = (ObjectNode) responseEntity.getBody();
                    if (Objects.isNull(body)) {
                        throw new PayException("response body cannot be resolved");
                    }
                    WxPayInfoResult wxPayInfoResult = this.getMapper().convertValue(body, WxPayInfoResult.class);
                    if(Objects.isNull(wxPayInfoResult)){
                        throw new PayException("response body parse wxPayInfoResult is wrong");
                    }
                    wxResponseEntity.setHttpStatus(responseEntity.getHttpStatus());
                    wxResponseEntity.setBody(wxPayInfoResult);
                })
                .request();
        return wxResponseEntity;
    }

    /**
     * @title jsPayFunction
     * @description js预支付function
     * @author lsk
     * @param: type
     * @param: payParams
     * @updateTime 2022/9/30 13:59
     * @return: org.apache.http.client.methods.HttpUriRequest
     */
    private HttpUriRequest jsPayFunction(WxPayV3Type type, WxPayTransParam payParams) {
       String appId = this.getConfig().getAppId();
       String mchId = this.getConfig().getMchId();
        payParams.setAppid(appId);
        payParams.setMchid(mchId);
        URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WxServerType.CHINA))
                .build()
                .toUri();
        return Post(uri, payParams);
    }


    /**
     * @title close
     * @description 关闭订单
     * @author lsk
     * @param: outTradeNo
     * @updateTime 2022/10/1 21:35
     * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<com.fasterxml.jackson.databind.node.ObjectNode>
     * @throws IOException
     */
    public WxResponseEntity<ObjectNode> close(String outTradeNo) throws IOException {
        WxResponseEntity<ObjectNode> wxResponseEntity = new WxResponseEntity<>();
        this.client().withType(WxPayV3Type.CLOSE, outTradeNo)
                .function(this::closeByOutTradeNoFunction)
                .consumer(wxResponseEntity::convert)
                .request();
        return wxResponseEntity;
    }

    /**
     * @title closeByOutTradeNoFunction
     * @description 关闭订单 function
     * @author lsk
     * @param: type
     * @param: outTradeNo
     * @updateTime 2022/10/1 21:22
     * @return: org.apache.http.client.methods.HttpUriRequest
     */
    private HttpUriRequest closeByOutTradeNoFunction(WxPayV3Type type, String outTradeNo) {

        WxPayProperties config = this.getConfig();
        Map<String, String> queryParams = new HashMap<>(1);
        queryParams.put("mchid", config.getMchId());

        URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WxServerType.CHINA))
                .build()
                .expand(outTradeNo)
                .toUri();
        return Post(uri, queryParams);
    }

    /**
     * @title refund
     * @description  申请退款API
     * @author lsk
     * @param: refundParam
     * @updateTime 2022/10/2 10:53
     * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<io.github.lhdxhl.wechatpay.result.WxRefundResult>
     * @throws IOException
     */
    public WxResponseEntity<WxRefundResult> refund(WxRefundParam refundParam) throws IOException {
        WxResponseEntity<WxRefundResult> wxResponseEntity = new WxResponseEntity<>();
        this.client().withType(WxPayV3Type.REFUND, refundParam)
                .function(((type, params) -> {
                    URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WxServerType.CHINA))
                            .build()
                            .toUri();
                    return Post(uri, params);
                }))
                .consumer(responseEntity->{
                    ObjectNode body = (ObjectNode) responseEntity.getBody();
                    if (Objects.isNull(body)) {
                        throw new PayException("response body cannot be resolved");
                    }
                    WxRefundResult wxRefundResult = this.getMapper().convertValue(body, WxRefundResult.class);
                    if(Objects.isNull(wxRefundResult)){
                        throw new PayException("response body parse wxRefundResult is wrong");
                    }
                    wxResponseEntity.setHttpStatus(responseEntity.getHttpStatus());
                    wxResponseEntity.setBody(wxRefundResult);
                })
                .request();
        return wxResponseEntity;
    }

    /**
     * @title queryRefundInfo
     * @description 查询退款订单数据
     * @author lsk
     * @param: outRefundNo
     * @updateTime 2022/10/2 11:00
     * @return: io.github.lhdxhl.wechatpay.v3.WxResponseEntity<io.github.lhdxhl.wechatpay.result.WxRefundResult>
     * @throws IOException
     */
    public WxResponseEntity<WxRefundResult> queryRefundInfo(String outRefundNo) throws IOException {
        WxResponseEntity<WxRefundResult> wxResponseEntity = new WxResponseEntity<>();
        this.client().withType(WxPayV3Type.QUERY_REFUND, outRefundNo)
                .function(((type, param) -> {
                    URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WxServerType.CHINA))
                            .build()
                            .expand(param)
                            .toUri();
                    return Get(uri);
                }))
                .consumer(responseEntity->{
                    ObjectNode body = (ObjectNode) responseEntity.getBody();
                    if (Objects.isNull(body)) {
                        throw new PayException("response body cannot be resolved");
                    }
                    WxRefundResult wxRefundResult = this.getMapper().convertValue(body, WxRefundResult.class);
                    if(Objects.isNull(wxRefundResult)){
                        throw new PayException("response body parse  query wxRefundResult is wrong");
                    }
                    wxResponseEntity.setHttpStatus(responseEntity.getHttpStatus());
                    wxResponseEntity.setBody(wxRefundResult);
                })
                .request();
        return wxResponseEntity;
    }

}
